<?php
class Model extends ModelBase
{
    protected $tableName;
    protected $connectionId;
    protected $connection;
    protected $joins = array();
    protected $forceIndexes = array();

    public function Model($table_name = null)
    {
        if (!$this->tableName) $this->tableName = $table_name ? $table_name : $this->GetLowercaseName();
        $this->LoadConnection();
        parent::__construct();
    }

    protected function LoadConnection()
    {
        if (!$this->connectionId) $this->connectionId = ConnectionModel::GetDefaultId();
        try
        {
            $this->connection = MysqlAdapter::Get($this->connectionId);
        }
        catch (Exception $ex)
        {
            throw new DBConnectionException($ex->getMessage());
        }
    }

	public function Find($condition = null, $fields = null, $orderby = null, $limit = null, &$page = null, &$count = null, $groupby = null)
	{
		$join = $this->GetJoins();
        $fields = $this->FormatFields($fields);
		return $this->connection->Select($this->tableName, $fields, $condition, $join, $orderby, $limit, $page, $count, $groupby, $this->forceIndexes);
	}

    protected function FormatFields($fields)
    {
        if (!$fields)
            return;
        else if (!is_array($fields))
            $fields = explode(', ', $fields);

        foreach ($fields as &$field)
        {
            if (!String::ContainsAny(array('.', '(', ' '), $field) && $this->tableName)
                $field = $this->tableName.'.'.$field;
        }
        return $fields;
    }

    public function ClearJoins()
    {
        $this->joins = array();
    }

	protected function GetJoins()
	{
        return implode(' ', $this->joins);
	}

    public function AddJoin($join_string)
    {
        $this->joins[] = $join_string;
    }

	public function Count($condition = null)
	{
		$join = $this->GetJoins();
		return $this->connection->Count($this->tableName, $condition, $join);
	}

    protected function LoadFields()
    {
        $this->fields = array();
        $fields = $this->connection->GetFields($this->tableName);
        foreach ($fields as $field)
        {
            $this->AddField(
                $field['name'],
                $field['type'],
                $field['nullable'],
                $field['is_pk'],
                $field['default'],
                $field['auto_increment']
            );
        }
        $this->LoadForeignKeys();
    }

    protected function LoadForeignKeys()
    {
        $fks = $this->connection->GetForeignKeys($this->tableName);
        foreach ($fks as $key => $fk)
        {
            $this->fields[$key]->SetForeignKey($fk);
        }
    }

    protected function DoInsert($item)
    {
        $values = $this->PrepareInsertValues($item, $fields);
        return $this->connection->Insert($this->tableName, $fields, $values);
    }

    protected function DoUpdate($item)
    {
        $condition = $this->GetUpdateCondition($item, $id);
        $update_values = $this->GetUpdateValues($item);
        if(empty($update_values)) return false;

        $this->connection->Update($this->tableName, $update_values, $condition);
        $new_id = $this->GetNewId($item);
        return $new_id ? $new_id : $id;
    }

    protected function DoDel($id)
    {
        $condition = $this->GetIdCondition($id);
        return $this->connection->Delete($this->tableName, $condition);
    }

    protected function GetUpdateCondition(&$item, &$id)
    {
        $pk = $this->GetPrimaryKey();
        $id = $pk ? $item[$pk] : null;
        if (empty($id))
            throw new Exception('Can not update because primary key is empty');

        if (!$this->IsNewId($item))
    	    unset($item[$pk]);

		return $this->GetIdCondition($id);
    }

    protected function IsNewId($item)
    {
        return isset($item[self::FIELD_NEW_ID]);
    }

    protected function GetUpdateValues($item)
    {
        $pk = $this->GetPrimaryKey();
    	$update_values = array();
        foreach ($item as $field_name => $value)
        {
        	if (isset($this->fields[$field_name]))
        	{
                if ($field_name == $pk && $this->IsNewId($item))
                    $value = $this->GetNewId($item);

	            $value = $this->FormatValue($field_name, $value);
	            $update_values[] = $this->connection->AddQuotes($field_name)." = $value";
			}
        }
        return implode(', ', $update_values);
    }

    protected function PrepareInsertValues($item, &$field_list)
    {
        $pk = $this->GetPrimaryKey();
        foreach ($item as $field_name => $value)
        {
            if (isset($this->fields[$field_name]))
            {
                if ($field_name == $pk && $this->IsNewId($item))
                    $value = $this->GetNewId($item);

                $fields[] = $field_name;
                $values[] = $this->FormatValue($field_name, $value);
            }
        }
        $field_list = implode(', ', $fields);
        return implode(', ', $values);
    }

    protected function FindExcludeId($condition = null)
    {
    	$fields = $this->fields;
    	unset($fields[$this->GetPrimaryKey()]);
		return $this->Find($condition, array_keys($fields));
    }

    public function GetSqlInsert($condition = null, $ref_field = null)
    {
		$data = $this->FindExcludeId($condition);
		if (empty($data)) return null;

        foreach ($data as $row)
		{
			$row_value[] = $this->GetSqlRowInsertValues($row, $ref_field);
		}
		$values = implode(",\n", $row_value);
		$fields = implode(',', array_keys($data[0]));
        $fields = $this->connection->AddQuotes($fields);
        $table = $this->connection->AddQuotes($this->tableName);
		return "INSERT INTO $table ($fields) VALUES\n$values;";
    }

    public function GetSqlInsertById($id)
    {
    	return $this->GetSqlInsert($this->GetIdCondition($id));
    }

    protected function GetSqlRowInsertValues($row, $ref_field = null)
    {
		foreach($row as $fieldname => $value)
		{
			$row[$fieldname] = $ref_field && $fieldname == $ref_field ? "@$ref_field" : $this->FormatValue($fieldname, $value);
		}
		return '('.implode(', ', $row).')';
    }

    protected function FormatValue($fieldname, $value)
    {
		return $this->connection->FormatValue($this->fields[$fieldname]->type, $value);
    }

    public function StartTransaction($id)
	{
		return $this->connection->StartTransaction($id);
	}

    public function CommitTransaction($id)
	{
		return $this->connection->CommitTransaction($id);
	}

    public function RollbackTransaction($id)
	{
		return $this->connection->RollbackTransaction($id);
	}

    public function ForceIndex($key)
    {
        $this->forceIndexes[$key] = $key;
    }

    protected function ClearForceIndexes()
    {
        $this->forceIndexes = array();
    }

    public function Truncate()
    {
        return $this->connection->Truncate($this->tableName);
    }

    public static function GetInstanceByName($table_name)
    {
        try
        {
            $model = ModelBase::GetInstanceByName($table_name);
        }
        catch (UsingFailedException $ex)
        {
            $model = new Model($table_name);
        }
        return $model;
    }

    public function FindDistinct($field, $orderby = null, $condition = null)
    {
        if (is_null($orderby))
            $orderby = $field;
        return $this->connection->SelectDistinct($this->tableName, $field, $orderby, $condition);
    }

    public function GetTableName()
    {
        return $this->tableName;
    }
}
?>